/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package digitaldreamers.games.ddmonsters.servers.gameserver.components;

import digitaldreamers.games.ddmonsters.servers.gameserver.components.PlayerComponent.InRoomSubstates;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author DaoLe
 */
public class DuelComponent {
    
    /**
     * Definition interface for a callback to be invoked when the duel starts.
     */
    public static interface IOnDuelStartListener {
        /**
         * The callback function. It is called when the duel starts.
         * @param turnPlayer The player whose turn is on.
         */
        public void onDuelStart(PlayerComponent turnPlayer);
    }
    
    /**
     * Definition interface for a callback to be invoked when turn phase changed.
     */
    public static interface IOnTurnPhaseChangedListener {
        /**
         * The callback function. It is called when the turn phase changed.
         * @param turnPlayer The active player.
         * @param currentPhase The current phase that is changed to.
         */
        public void onTurnPhaseChanged(PlayerComponent turnPlayer, TurnPhases currentPhase);
    }
    
    /**
     * Definition interface for a callback to be invoked when turn changed.
     */
    public static interface IOnTurnChangedListener {
        /**
         * The callback function. It is called when the turn changed.
         * @param turnPlayer The active player.
         */
        public void onTurnChanged(PlayerComponent turnPlayer);
    }
    
    /**
     * Definition interface for a callback to be invoked when the duel ends.
     */
    public static interface IOnDuelEndListener {
        /**
         * The callback function. It is called when the duel ends.
         * @param winner The player who won.
         */
        public void onDuelEnd(PlayerComponent winner);
    }
    
    public static enum DuelStates {
        INITIALIZED,
        STARTED,
        DUELING,
        ENDED
    }
    
    public static enum TurnPhases {
        DRAW_PHASE(0),
        //STANDBY_PHASE(1),
        MAIN_PHASE(1),
        //MAIN_PHASE_1(2),
        BATTLE_PHASE(2),
        //MAIN_PHASE_2(4),
        END_PHASE(3);
        
        private int m_iPhase;
        
        private TurnPhases(int iPhase) {
            m_iPhase = iPhase;
        }
 
        public int getPhase() {
            return m_iPhase;
        }
    }
    
    private static final String PHASE_IS_NOT_CORRECT_ERROR_MESSAGE = "Phase can not be null or same as the current phase.";
    
    private RoomComponent m_Room = null;
    private PlayerComponent m_TurnPlayer = null;
    private PlayerComponent m_RoomMaster = null;
    private PlayerComponent m_RoomGuest = null;
    private DuelFieldComponent m_DuelField = null;
    private DuelStates m_State = null;
    private TurnPhases m_CurrentPhase = null;
    private boolean m_bIsFirstTurn = true;
    private boolean m_bHasTurnPlayerSummonedMonster = false;
    private HashMap<PlayerComponent, PlayerDuelStatusComponent> playerStatusMap = null;
    //Listeners on the duel start event.
    private ArrayList<IOnDuelStartListener> m_DuelStartListeners = null;
    //Listeners on the turn change event.
    private ArrayList<IOnTurnChangedListener> m_TurnChangedListeners = null;
    //Listeners on the turn phase change event.
    private ArrayList<IOnTurnPhaseChangedListener> m_TurnPhaseChangedListeners = null;
    //Listeners on the duel end event.
    private ArrayList<IOnDuelEndListener> m_DuelEndListeners = null;
    private final Object m_LockObject;
    
    public PlayerComponent getOpponent(PlayerComponent player) {
        return m_Room.getOpponent(player);
    }
    
    public DuelStates getState() {
        return m_State;
    }
    
    public DuelComponent(RoomComponent room) {
        m_Room = room;
        playerStatusMap = new HashMap<>();
        m_LockObject = new Object();
        initialize();
    }
    
    public final void initialize() {
        m_bIsFirstTurn = true;
        m_bHasTurnPlayerSummonedMonster = false;
        m_CurrentPhase = null;
        m_RoomMaster = m_Room.getRoomMaster();
        m_RoomGuest = m_Room.getRoomGuest();
        m_RoomMaster.setDuel(this);
        m_RoomGuest.setDuel(this);
        m_TurnPlayer = m_RoomMaster;
        m_RoomMaster.prepareForDuel();
        m_RoomGuest.prepareForDuel();
        m_DuelField = new DuelFieldComponent(0, m_RoomMaster, m_RoomGuest);
        playerStatusMap.clear();
        PlayerDuelStatusComponent roomMasterStatus = new PlayerDuelStatusComponent(m_RoomMaster);
        PlayerDuelStatusComponent roomGuestStatus = new PlayerDuelStatusComponent(m_RoomGuest);
        playerStatusMap.put(m_RoomMaster, roomMasterStatus);
        playerStatusMap.put(m_RoomGuest, roomGuestStatus);
        m_State = DuelStates.INITIALIZED;
    }
    
    public boolean isTurnPlayer(PlayerComponent player) {
        return m_TurnPlayer == player;
    }
    
    public void playerClientIsReady(PlayerComponent player) {
        synchronized(m_LockObject) {
            if(getOpponent(player).getInRoomSubstate() == InRoomSubstates.CLIENT_READY) {
                start();
            }
        }
    }
    
    private void start() {
        m_State = DuelStates.STARTED;
        fireDuelStartEvent(m_TurnPlayer);
        m_State = DuelStates.DUELING;
        changePhase(TurnPhases.DRAW_PHASE);
    }
    
    public void changePhase(TurnPhases phase) throws IllegalArgumentException {
        if(phase == null || phase == m_CurrentPhase) {
            throw new IllegalArgumentException(PHASE_IS_NOT_CORRECT_ERROR_MESSAGE);
        }
        m_CurrentPhase = phase;
        fireTurnPhaseChangedEvent(m_TurnPlayer, m_CurrentPhase);
        if(m_CurrentPhase == TurnPhases.DRAW_PHASE) {
            m_TurnPlayer.drawCard();
            changePhase(TurnPhases.MAIN_PHASE);
        }
        else if(m_CurrentPhase == TurnPhases.END_PHASE) {
            if(m_bIsFirstTurn) {
                m_bIsFirstTurn = false;
            }
            m_TurnPlayer = getOpponent(m_TurnPlayer);
            m_bHasTurnPlayerSummonedMonster = false;
            fireTurnChangedEvent(m_TurnPlayer);
            changePhase(TurnPhases.DRAW_PHASE);
        }
    }
    
    public ArrayList<PlayerCardComponent> drawCards(PlayerComponent player, int iNumber) {
        ArrayList<PlayerCardComponent> drawnCards = new ArrayList<>(iNumber);
        if(iNumber > 0 && isTurnPlayer(player)) {
            PlayerDuelStatusComponent playerDuelStatus = playerStatusMap.get(player);
            if(playerDuelStatus != null) {
                DeckComponent playerDeck = playerDuelStatus.getDuelDeck();
                for(int i = 0; i < iNumber; i++) {
                    if(playerDuelStatus.getNumberOfCardsInHand() < PlayerDuelStatusComponent.MAX_NUMBER_OF_CARDS_IN_HAND) {
                        PlayerCardComponent card = playerDeck.drawCard();
                        if(card == null) {
                            break;
                        }
                        drawnCards.add(card);
                        playerDuelStatus.addCardToHand(card);
                    }
                    else {
                        break;
                    }
                }
            }
        }
        return drawnCards;
    }
    
    public boolean summonMonster(PlayerComponent player, int iHandIndex, int iSummonCoordinateX, int iSummonCoordinateY) {
        boolean bResult = false;
        if(isTurnPlayer(player) && !m_bHasTurnPlayerSummonedMonster) {
            PlayerDuelStatusComponent playerDuelStatus = playerStatusMap.get(player);
            if(playerDuelStatus != null) {
                PlayerCardComponent card = playerDuelStatus.getCardInHandAtIndex(iHandIndex);
                if(card != null && card instanceof MonsterCardComponent) {
                    MonsterCardComponent monsterCard = (MonsterCardComponent)card;
                    bResult = m_DuelField.summonMonster(player, monsterCard, iSummonCoordinateX, iSummonCoordinateY);
                    if(bResult) {
                        playerDuelStatus.removeCardFromHandByIndex(iHandIndex);
                        m_bHasTurnPlayerSummonedMonster = true;
                    }
                }
            }
        }
        return bResult;
    }
    
    public boolean moveMonster(PlayerComponent player, int iFromCoordinateX, int iFromCoordinateY, int iToCoordinateX, int iToCoordinateY) {
        boolean bResult = false;
        if(isTurnPlayer(player)) {
            MonsterCardComponent monsterCard = m_DuelField.getMyMonsterOnField(player, iFromCoordinateX, iFromCoordinateY);
            if(monsterCard != null) {
                if(monsterCard.getMovementState()) {
                    bResult = m_DuelField.moveMonster(player, monsterCard, iToCoordinateX, iToCoordinateY, false);
                    if(bResult) {
                        monsterCard.setMovementState(false);
                    }
                }
            }
        }
        return bResult;
    }
    
    public boolean attack(PlayerComponent player, int iAttackMonsterCoordinateX, int iAttackMonsterCoordinateY, int iTargetMonsterCoordinateX, int iTargetMonsterCoordinateY) {
        boolean bResult = false;
        MonsterCardComponent attackMonster = m_DuelField.getMyMonsterOnField(player, iAttackMonsterCoordinateX, iAttackMonsterCoordinateY);
        if(attackMonster != null) {
            MonsterCardComponent targetMonster = m_DuelField.getMyMonsterOnField(getOpponent(player), iAttackMonsterCoordinateX, iAttackMonsterCoordinateY);
            if(targetMonster != null) {
                bResult = canAttackInRange(attackMonster, iAttackMonsterCoordinateX, iAttackMonsterCoordinateY, iTargetMonsterCoordinateX, iTargetMonsterCoordinateY);
                if(bResult) {
                    int iDamage = calculateAttackDamage(attackMonster, targetMonster);
                }
            }
        }
        return bResult;
    }
    
    private boolean canAttackInRange(MonsterCardComponent attackMonster, int iAttackMonsterCoordinateX, int iAttackMonsterCoordinateY, int iTargetMonsterCoordinateX, int iTargetMonsterCoordinateY) {
        int iMonsterAttackRange = attackMonster.getDuelAttackRange();
        int iAttackRange = Math.abs(iTargetMonsterCoordinateX - iAttackMonsterCoordinateX) + Math.abs(iTargetMonsterCoordinateY - iAttackMonsterCoordinateY);
        return iMonsterAttackRange >= iAttackRange;
    }
    
    private int calculateAttackDamage(MonsterCardComponent attackMonster, MonsterCardComponent targetMonster) {
        int iDamage;
        int iAttackPoint = attackMonster.getDuelAttackPoint();
        int iDefensePoint = attackMonster.getDefensePoint();
        
        // Should be edited
        iDamage = (int)iAttackPoint/iDefensePoint * 100;
        return iDamage;
    }
    
    /**
     * Register a new listener for the duel start event.
     * @param listener The new listener of the event.
     * @return The result whether or not the listener is registered.
     */
    public boolean addDuelStartListener(IOnDuelStartListener listener) {
        if (!m_DuelStartListeners.contains(listener)) {
            return m_DuelStartListeners.add(listener);
        }
        return false;
    }
    
    /**
     * Unregister a listener for the duel start event.
     * @param listener The registered listener of the event.
     * @return The result whether or not the listener is unregistered.
     */
    public boolean removeDuelStartListener(IOnDuelStartListener listener) {
        if (m_DuelStartListeners.contains(listener)) {
            return m_DuelStartListeners.remove(listener);
        }
        return false;
    }
    
    /**
     * Fire the duel start event.
     * @param turnPlayer The player whose turn is on.
     */
    private void fireDuelStartEvent(PlayerComponent turnPlayer) {
        IOnDuelStartListener[] listeners = new IOnDuelStartListener[m_DuelStartListeners.size()];
        listeners = m_DuelStartListeners.toArray(listeners);
        for (IOnDuelStartListener listener : listeners) {
            listener.onDuelStart(turnPlayer);
        }
    }
    
    /**
     * Register a new listener for the turn phase change event.
     * @param listener The new listener of the event.
     * @return The result whether or not the listener is registered.
     */
    public boolean addTurnPhaseChangedListener(IOnTurnPhaseChangedListener listener) {
        if (!m_TurnPhaseChangedListeners.contains(listener)) {
            return m_TurnPhaseChangedListeners.add(listener);
        }
        return false;
    }
    
    /**
     * Unregister a listener for the turn phase change event.
     * @param listener The registered listener of the event.
     * @return The result whether or not the listener is unregistered.
     */
    public boolean removeTurnPhaseChangedListener(IOnTurnPhaseChangedListener listener) {
        if (m_TurnPhaseChangedListeners.contains(listener)) {
            return m_TurnPhaseChangedListeners.remove(listener);
        }
        return false;
    }
    
    /**
     * Fire the turn phase change event.
     * @param turnPlayer The player whose turn is on.
     * @param currentPhase The phase that is changed to.
     */
    private void fireTurnPhaseChangedEvent(PlayerComponent turnPlayer, TurnPhases currentPhase) {
        IOnTurnPhaseChangedListener[] listeners = new IOnTurnPhaseChangedListener[1];
        listeners = m_TurnPhaseChangedListeners.toArray(listeners);
        for (IOnTurnPhaseChangedListener listener : listeners) {
            listener.onTurnPhaseChanged(turnPlayer, currentPhase);
        }
    }
    
    /**
     * Register a new listener for the turn change event.
     * @param listener The new listener of the event.
     * @return The result whether or not the listener is registered.
     */
    public boolean addTurnChangedListener(IOnTurnChangedListener listener) {
        if (!m_TurnChangedListeners.contains(listener)) {
            return m_TurnChangedListeners.add(listener);
        }
        return false;
    }
    
    /**
     * Unregister a listener for the turn change event.
     * @param listener The registered listener of the event.
     * @return The result whether or not the listener is unregistered.
     */
    public boolean removeTurnChangedListener(IOnTurnChangedListener listener) {
        if (m_TurnChangedListeners.contains(listener)) {
            return m_TurnChangedListeners.remove(listener);
        }
        return false;
    }
    
    /**
     * Fire the turn change event.
     * @param turnPlayer The player whose turn is on.
     */
    private void fireTurnChangedEvent(PlayerComponent turnPlayer) {
        IOnTurnChangedListener[] listeners = new IOnTurnChangedListener[m_TurnChangedListeners.size()];
        listeners = m_TurnChangedListeners.toArray(listeners);
        for (IOnTurnChangedListener listener : listeners) {
            listener.onTurnChanged(turnPlayer);
        }
    }
    
    /**
     * Register a new listener for the duel end event.
     * @param listener The new listener of the event.
     * @return The result whether or not the listener is registered.
     */
    public boolean addDuelEndListener(IOnDuelEndListener listener) {
        if (!m_DuelEndListeners.contains(listener)) {
            return m_DuelEndListeners.add(listener);
        }
        return false;
    }
    
    /**
     * Unregister a listener for the duel end event.
     * @param listener The registered listener of the event.
     * @return The result whether or not the listener is unregistered.
     */
    public boolean removeDuelEndListener(IOnDuelEndListener listener) {
        if (m_DuelEndListeners.contains(listener)) {
            return m_DuelEndListeners.remove(listener);
        }
        return false;
    }
    
    /**
     * Fire the duel end change event.
     * @param winner The winner.
     */
    private void fireDuelEndEvent(PlayerComponent winner) {
        IOnDuelEndListener[] listeners = new IOnDuelEndListener[m_DuelEndListeners.size()];
        listeners = m_DuelEndListeners.toArray(listeners);
        for (IOnDuelEndListener listener : listeners) {
            listener.onDuelEnd(winner);
        }
    }
}
